import {parseHtml, ELEMENT_TYPE} from "./parse.js";

function genProps(attrs) {
    let str = ``
    attrs.forEach(attr => {
        // 遍历行内元素，变成 {id:"app",style:{"color":"red","font-size":"20px"}} 这种效果
        if (attr.name === "style") {
            let obj = {}
            attr.value.split(";").forEach(sItem => {
                let [key, value] = sItem.split(":")
                obj[key] = value.trim()
            })
            attr.value = obj
        }
        str += `${JSON.stringify(attr.name)}:${JSON.stringify(attr.value)},`
    })
    str = `{${str.slice(0, -1)}}`
    return str
}

function genChildren(children) {
    return children.map(child => gen(child)).join(",")
}

// 匹配花括号中的的内容
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g;

function gen(child) {
    // 匹配到的是一个元素
    if (child.type === ELEMENT_TYPE) {
        return codegen(child)
    } else {
        let text = child.text
        // 匹配到的是一个纯文本，不是用花括号包裹起来的文本时，会走下面的方法，返回一个 _v 函数，用于创建文本节点
        if (!defaultTagRE.test(text)) {
            return `_v(${JSON.stringify(text)})`
        } else {
            // 如果这个文本元素包含有花括号包裹的数据，就会走else方法
            let token = []
            let match;
            let lastIndex = 0
            // 设置正则的lastIndex从0开始，也就是从文本的最前面开始进行匹配
            defaultTagRE.lastIndex = 0
            // exec方法会匹配到一个花括号数据时就会走一次循环，然后继续往后匹配剩余的花括号
            // 只到匹配结束，会返回null，然后退出循环
            while (match = defaultTagRE.exec(text)) {
                // match.index返回的是当前匹配到的数据的下标
                let index = match.index
                // 如果出现匹配到的数据下标大于lastIndex下标，则表示如下情况
                // hello {{ age }},表示这个花括号前面还有文本，我们需要先把花括号前面的文本放在token中
                if (index > lastIndex) {
                    token.push(JSON.stringify(text.slice(lastIndex, index)))
                }
                // 然后再吧花括号里面的变量放在token中，并且用_s函数接收这个变量
                token.push(`_s(${match[1].trim()})`)
                // 更新lastIndex
                lastIndex = index + match[0].length
            }
            // 当exec匹配完成后，出现lastIndex < text.length的情况
            // 表示最后一个花括号后面还有普通文本，例如：{{ age }} word,则我们需要吧后面的文本也放在token中
            if (lastIndex < text.length) {
                token.push(JSON.stringify(text.slice(lastIndex)))
            }
            // 最后返回一个 _v() 函数，里面的token使用+拼接返回
            return `_v(${token.join("+")})`
        }
    }
}

function codegen(ast) {
    // _c("div",{xxx:xxx,xxx:xxx})
    // 第一个参数是需要创建的元素，第二个是对应的属性
    let code = `_c(
        ${JSON.stringify(ast.tag)},
        ${ast.attrs.length ? genProps(ast.attrs) : null},
        ${ast.children.length ? genChildren(ast.children) : null}
    )`
    return code
}


export function compilerToFunction(template) {
    // 1. 解析DOM，转化成AST语法树
    let ast = parseHtml(template)

    // 2. 根据AST语法树生成cvs字符串
    let code = codegen(ast)

    // 3.根据字符串生成render方法
    code = `with(this){return ${code}}`
    let render = new Function(code)

    return render
}